﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CafeOnline.Common;
using CafeOnline.Common.Criteria;
using CafeOnline.MvcFramework.Composition;
using CafeOnline.MvcFramework.Interfaces;
using CafeOnline.MvcFramework.Web.Json;

namespace CafeOnline.MvcFramework.Grid {

    [InheritedExport(typeof(IColumnFactory))]
    public abstract class ExtendedColumnOld<TEntityType, TCriteria> : IColumnFactory, IPartImportsSatisfiedNotification
        where TEntityType : BaseEntity
        where TCriteria : BaseCriteria {
        #region IProperties
        
        public IColumnFactoryMetadata Metadata {
            get {
                return MetadataExplorer.GetMetadata<IColumnFactoryMetadata>(this.GetType());
            }
        }

        public ColumnType ColumnType {
            get;
            private set;
        }

        #endregion IProperties
        #region IFields

        private Func<TEntityType, IDictionary<string, object>, object> m_fetchData;
        private Action<TCriteria, string> m_filtering;
        private Action<TCriteria, SortOrder> m_sorting;

        protected Func<string, Func<TEntityType, bool>> m_objectFiltering;
        protected Func<TEntityType, object> m_objectSorting;

        protected List<Action<Column>> m_gridFormatting;

        #endregion IFields
        #region IConstructors

        public ExtendedColumnOld() {
            m_gridFormatting = new List<Action<Column>>();
        }

        #endregion IConstructors
        #region IMethods

        public Column Create(string gridId) {
            Column c = new Column(gridId, this.Metadata.ColumnName, this);
            if (!string.IsNullOrEmpty(Metadata.LabelResourceKey)) {
                c.SetLabel(Localization.Resources.ResourceManager.GetString(Metadata.LabelResourceKey));
            } else if (!string.IsNullOrEmpty(Metadata.Label)) {
                c.SetLabel(Metadata.Label);
            }
            if (!string.IsNullOrEmpty(Metadata.SortingName)) {
                c.SetSortable(true);
                c.SetFirstSortOrder(SortOrder.asc);
            }
            if (Metadata.Hidden) {
                c.SetHidden(true);
            }

            
            m_gridFormatting.ForEach(gF => gF(c));

            return c;
        }

        public void AddFormatting(Action<Column> columnFormatting) {
            m_gridFormatting.Add(columnFormatting);
        }


        internal void SetFetchData(Type dataType, Func<TEntityType, IDictionary<string, object>, object> fetchData) {

            //Use the DataType to provide information on how to format the row
            ColumnType = new ColumnType(dataType);

            switch (ColumnType.DataType) {
                case ColumnDataType.Date: {
                        var dateFormat = new ObjectOptions();
                        dateFormat.Add("newformat", Localization.Resources.DateGridFormat);
                        dateFormat.Add("srcformat", Localization.Resources.DatetimeGridSourceFormat);
                        m_gridFormatting.Add((c) => c.SetFormatter(Formatters.date, dateFormat));
                        m_fetchData = (r, d) => fetchData(r, d);
                    }
                    break;
                case ColumnDataType.DateTime: {
                        m_fetchData = (r, d) => {
                            DateTime? val = fetchData(r, d) as Nullable<DateTime>;
                            if (!val.HasValue) {
                                return null;
                            } else {
                                return val.Value.ToShortDateString();
                            }
                        };

                        var dateFormat = new ObjectOptions();
                        dateFormat.Add("newformat", Localization.Resources.DateTimeGridFormat);
                        dateFormat.Add("srcformat", Localization.Resources.DatetimeGridSourceFormat);
                        if (ColumnType.Nullable) {
                            dateFormat.Add("defaultValue", null);
                        }
                        m_gridFormatting.Add((c) => c.SetFormatter(Formatters.date, dateFormat));
                    }
                    break;
                case ColumnDataType.FixedDateTime: {
                        var dateFormat = new ObjectOptions();
                        dateFormat.Add("newformat", Localization.Resources.DateTimeGridFormat);
                        dateFormat.Add("srcformat", Localization.Resources.DatetimeGridSourceFormat);
                        m_gridFormatting.Add((c) => c.SetFormatter(Formatters.date, dateFormat));
                        m_fetchData = (r, d) => fetchData(r, d);
                    }
                    break;

                case ColumnDataType.Enum:
                    //TODO: Perform Localization of the Enum
                    m_fetchData = (r, d) => fetchData(r, d);
                    break;
                case ColumnDataType.Decimal:
                    AddFormatting(c => c.SetFormatter(Formatters.number));
                    AddFormatting(c => c.SetAlign(Align.right));
                    m_fetchData = (r, d) => fetchData(r, d);
                    break;
                case ColumnDataType.Number:
                    AddFormatting(c => c.SetFormatter(Formatters.integer));
                    AddFormatting(c => c.SetAlign(Align.right));
                    m_fetchData = (r, d) => fetchData(r, d);
                    break;
                case ColumnDataType.Boolean:
                    AddFormatting(c => c.SetFormatter(Formatters.checkbox));
                    m_fetchData = (r, d) => fetchData(r, d);
                    break;
                default:
                    m_fetchData = (r, d) => fetchData(r, d);
                    break;
            }
        }

        public ExtendedColumnOld<TEntityType, TCriteria> SetFetchData<TDataType>(Func<TEntityType, IDictionary<string, object>, TDataType> fetchData) {

            //Use the DataType to provide information on how to format the row
            Type t = typeof(TDataType);
            ColumnType = new ColumnType(t);
            switch (ColumnType.DataType) {
                case ColumnDataType.Date: {
                        var dateFormat = new ObjectOptions();
                        dateFormat.Add("newformat", Localization.Resources.DateGridFormat);
                        dateFormat.Add("srcformat", Localization.Resources.DatetimeGridSourceFormat);
                        AddFormatting(c => c.SetFormatter(Formatters.date, dateFormat));
                        m_fetchData = (r, d) => fetchData(r, d);
                    }
                    break;
                case ColumnDataType.DateTime: {
                        m_fetchData = (r, d) => {
                            DateTime? val = fetchData(r, d) as Nullable<DateTime>;
                            if (!val.HasValue) {
                                return null;
                            } else {
                                return val.Value;
                            }
                        };

                        var dateFormat = new ObjectOptions();
                        dateFormat.Add("newformat", Localization.Resources.DateTimeGridFormat);
                        dateFormat.Add("srcformat", Localization.Resources.DatetimeGridSourceFormat);
                        if (ColumnType.Nullable) {
                            dateFormat.Add("defaultValue", null);
                        }
                        AddFormatting(c => c.SetFormatter(Formatters.date, dateFormat));
                    }
                    break;
                case ColumnDataType.FixedDateTime: {
                        var dateFormat = new ObjectOptions();
                        dateFormat.Add("newformat", Localization.Resources.DateTimeGridFormat);
                        dateFormat.Add("srcformat", Localization.Resources.DatetimeGridSourceFormat);
                        AddFormatting(c => c.SetFormatter(Formatters.date, dateFormat));
                        m_fetchData = (r, d) => fetchData(r, d);
                    }
                    break;

                case ColumnDataType.Enum:
                    //TODO: Perform Localization of the Enum
                    m_fetchData = (r, d) => fetchData(r, d);
                    break;
                case ColumnDataType.Decimal:
                    AddFormatting(c => c.SetFormatter(Formatters.number));
                    AddFormatting(c => c.SetAlign(Align.right));
                    m_fetchData = (r, d) => fetchData(r, d);
                    break;
                case ColumnDataType.Number:
                    AddFormatting(c => c.SetFormatter(Formatters.integer));
                    AddFormatting(c => c.SetAlign(Align.right));
                    m_fetchData = (r, d) => fetchData(r, d);
                    break;
                case ColumnDataType.Boolean:
                    AddFormatting(c => c.SetFormatter(Formatters.checkbox));
                    m_fetchData = (r, d) => fetchData(r, d);
                    break;
                default:
                    m_fetchData = (r, d) => fetchData(r, d);
                    break;
            }
            return this;
        }

        public ExtendedColumnOld<TEntityType, TCriteria> SetFiltering(Action<TCriteria, string> filtering) {
            m_filtering = filtering;
            AddFormatting(c => c.SetSearch(true));
            return this;
        }

        public ExtendedColumnOld<TEntityType, TCriteria> SetObjectFiltering(Func<string, Func<TEntityType, bool>> filtering) {
            m_objectFiltering = filtering;
            return this;
        }

        public ExtendedColumnOld<TEntityType, TCriteria> SetSorting(Action<TCriteria, SortOrder> sorting) {
            m_sorting = sorting;
            AddFormatting(c => c.SetSortable(true));
            return this;
        }

        public ExtendedColumnOld<TEntityType, TCriteria> SetObjectSorting(Func<TEntityType, object> sorting) {
            m_objectSorting = sorting;
            return this;
        }

        public object GetData(BaseEntity row, IDictionary<string, object> additionalData) {
            return GetData((TEntityType)row, additionalData);
        }

        public object GetData(TEntityType row, IDictionary<string, object> additionalData) {
            return m_fetchData(row, additionalData);
        }

        public void FilterData(object criteria, Rule rule) {
            if (rule.field == Metadata.ColumnName) {
                if (m_filtering != null) {
                    FilterData((TCriteria)criteria, rule);
                }
            }
        }

        private void FilterData(TCriteria criteria, Rule rule) {
            m_filtering(criteria, rule.data);
        }

        public Func<object, bool> BuildFilterObject(Rule rule) {
            if ((rule.field == Metadata.ColumnName) && (m_objectFiltering != null)) {
                return o => m_objectFiltering(rule.data)((TEntityType)o);
            }
            return null;
        }

        public void SortData(object criteria, SortOrder order) {
            SortData((TCriteria)criteria, order);
        }

        private void SortData(TCriteria criteria, SortOrder order) {
            //if (m_sorting != null) {
            //    m_sorting((TCriteria)criteria, order);
            //} else if (!string.IsNullOrWhiteSpace(Metadata.SortingName)) {
            //    criteria.AddSorting(Metadata.SortingName, order, false);
            //}
        }

        public void SetTemplateFormatter(string templateFunctionName) {
            var options = new ObjectOptions();
            options.Add("template", new JavascriptFunction(templateFunctionName));
            AddFormatting(c => c.SetFormatter(Formatters.templateFormatter, options));
        }

        public void SetCustomEditing(string formCreator, string valueAccessor) {
            AddFormatting(c => {
                c.SetEditType(EditType.custom);
                var options = new ObjectOptions();
                options.Add("custom_element", new JavascriptFunction(formCreator));
                options.Add("custom_value", new JavascriptFunction(valueAccessor));
                c.Add("editoptions", options);
            });
        }

        public void SetSelectEditing(IEnumerable<KeyValuePair<object, string>> selectVal) {
            AddFormatting(c => {
                c.SetEditType(EditType.select);
                c.SetFormatter(Formatters.select);
                var options = new ObjectOptions();
                var values = string.Empty;
                selectVal.Aggregate(values, (res, a) => {
                    if (res.Length > 0) {
                        res += ";";
                    }
                    return res + a.Key.ToString() + ":" + a.Value;
                });
                options.Add("value", values);
                c.Add("editoptions", options);
            });

        }

        public Func<IEnumerable, IEnumerable> BuildSortObject(SortOrder order) {
            if (m_objectSorting != null) {
                return list => {
                    if (list is IOrderedEnumerable<TEntityType>) {
                        var t = (IOrderedEnumerable<TEntityType>)list;
                        if (order == SortOrder.asc) {
                            return t.ThenBy(m_objectSorting);
                        } else {
                            return t.ThenByDescending(m_objectSorting);
                        }
                    } else if (list is IEnumerable<TEntityType>) {
                        var t = (IEnumerable<TEntityType>)list;
                        if (order == SortOrder.asc) {
                            return t.OrderBy(m_objectSorting);
                        } else {
                            return t.OrderByDescending(m_objectSorting);
                        }
                    } else {
                        return list;
                    }
                };
            } else {
                return list => list;
            }
        }

        void IPartImportsSatisfiedNotification.OnImportsSatisfied() {
            Initialize();
        }

        /// <summary>
        /// This methods is called on initialization once all imports are satisfied to configure the column based on several settings.
        /// </summary>
        protected virtual void Initialize() {

        }

        public void SetSearch(bool searchable) {
            AddFormatting(c => c.SetSearch(searchable));
        }

        public void SetSearchType(Searchtype type) {
            AddFormatting(c => c.SetSearchType(type));
        }

        public void SetFormatter(Formatters formatter) {
            AddFormatting(c => c.SetFormatter(formatter));
        }

        public void SetFormatter(Formatters formatter, ObjectOptions options) {
            AddFormatting(c => c.SetFormatter(formatter, options));
        }

        public void SetSearchInit(object init) {
            AddFormatting(c => c.SetSearchInit(init));
        }

        public void SetSearchTerm(IEnumerable<KeyValuePair<int, string>> searchTerms) {
            AddFormatting(c => c.SetSearchTerms(searchTerms));
        }

        public void SetWidth(int width) {
            AddFormatting(c => c.SetWidth(width));
        }

        public void SetCustomFormatter(CustomFormatter cF) {
            AddFormatting(c => c.SetCustomFormatter(cF));
        }

        #endregion IMethods
    }
}
